Scope the debrief lease to agent kind so RunDebriefer and CautionDrafter coexist#556
Merged
Conversation
The lease-winner scan matched ANY DecisionDebriefRequested marker by terminal_event_id, so RunDebriefer and CautionDrafter, which both subscribe to all four terminal Run events, contended for one lease: whichever applied second saw the other as the winner and wrote a Conflicted audit Decision instead of doing its job. In the full app that left the second-registered agent systematically dead; the defect stayed invisible because every subscriber test drives one agent at a time. The caution_drafter call-site comment claimed independence the scan never implemented. The design memo's contention target is multi-agent pollution among agents doing the SAME job (rainbow-deploy variants of one debriefer, different LLM backends of one drafter), and the Agent aggregate's kind already names that job, so the marker now carries debriefer_kind (additive payload field) and the winner scan filters by it. Kindless legacy markers contend with every kind, preserving replay behavior for pre-scoping streams. Same-kind semantics are unchanged: variants race on stream version, same-agent retries stay idempotent via the uuid5 event_id. The one-shot append also became a bounded load-scan-append loop: with kinds independent, another kind's lease landing between load and append is now the COMMON version-bump, and treating it as a degenerate loss would drop real work. A non-competing bump retries (3 attempts); exhaustion keeps the old (False, None) exit. decided_by log values are now scan, append, and retry_exhaustion. Verified by test_apply_coexists_with_run_debriefer_on_same_terminal_event, which reproduces the contention on the pre-fix code (CautionDrafter wrote CautionDraftConflicted; both agents now write substantive Decisions), plus primitive-level pins for cross-kind independence, legacy wildcard, retry-past-non-competitor, and retry exhaustion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Coverage reportClick to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Supersedes #555, which GitHub auto-closed when its base branch (
worktree-budget-metering, PR #554) was merged and deleted; the branch is now rebased onto main with the single lease commit.Summary
Fixes a pre-existing defect surfaced by #554's gate review: the debrief-lease winner scan matched ANY
DecisionDebriefRequestedmarker byterminal_event_id, so RunDebriefer and CautionDrafter, which both subscribe to all four terminal Run events, contended for one lease. Whichever applied second saw the other as the winner and wrote aDebriefConflicted/CautionDraftConflictedaudit Decision instead of doing its job, leaving the second-registered agent systematically dead in a full deployment. Every subscriber test drove one agent at a time, so the defect was invisible; the caution_drafter call-site comment claimed an independence the code never implemented.Fix
Contention is now scoped by agent kind (the design memo's target is multi-agent pollution among agents doing the same job: rainbow-deploy variants of one debriefer, different LLM backends of one drafter):
DecisionDebriefRequestedgains an additivedebriefer_kind: str | Nonepayload field; the winner scan filters by it. Kindless legacy markers contend with every kind (wildcard), so pre-scoping streams replay unchanged.(False, None)exit.decided_bylog values:scan/append/retry_exhaustion.Test plan
test_apply_coexists_with_run_debriefer_on_same_terminal_eventfails on pre-fix code (CautionDrafter wroteCautionDraftConflicted), passes now with both agents writing substantive Decisions.🤖 Generated with Claude Code